#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h> 
#include "crc32.h"

//         -msse4.2
// gcc -O3 -msse4.2 -std=gnu99 crc32.c 
//         -msse4.2

#define __builtin_prefetch(a,b,c)

#define CRC32(crc, buf, index) crc = __builtin_ia32_crc32di(crc, buf[index])
#define CRC32_B(crc, byte_in) crc = __builtin_ia32_crc32qi(crc, byte_in)

// size < 4k, size not need to be power of 8
uint32_t crc32_n(const void *buf_, uint32_t size)
{
	uint64_t* buf = (uint64_t*)buf_;
	uint32_t uint64_nb = size / sizeof(uint64_t);
	uint32_t byte_nb = size % sizeof(uint64_t);
	uint64_t crc = 0;
	for(uint32_t j=0; j < uint64_nb; ++j)
	{
		crc = __builtin_ia32_crc32di(crc, *buf);
		buf++;
	}
	char *buf_b = (char*)buf;
	for(uint32_t j=0; j < byte_nb; ++j)
	{
		CRC32_B(crc, buf_b[j]);
		buf_b++;
	}
	return (uint32_t)crc;
}

void crc32_4k_sequence(char* buf_, uint32_t size, uint32_t* out_crc32)
{
	uint64_t* buf = (uint64_t*)buf_;
	__builtin_prefetch(buf, 0, 0);
	while((char*)buf < buf_ + size)
	{
		uint64_t crc = 0;
		for (uint32_t j=0; j < CRC32_BLOCK_LENGTH/8; ++j)
		{
			CRC32(crc, buf, 0);
			CRC32(crc, buf, 1);
			CRC32(crc, buf, 2);
			CRC32(crc, buf, 3);
			CRC32(crc, buf, 4);
			CRC32(crc, buf, 5);
			CRC32(crc, buf, 6);
			CRC32(crc, buf, 7);
			buf += 8;
			__builtin_prefetch(buf, 0, 0);
		}
		*out_crc32++ = (uint32_t)crc;
	}
}

#define CRC32_P2(crc, buf, index) { \
	CRC32(crc##1, buf##1, index); 	\
	CRC32(crc##2, buf##2, index); 	\
}

void crc32_4k_sequence_p2(char* buf_, uint32_t size, uint32_t* out_crc32)
{
	uint32_t size0 = size;
	size /= 2;
	uint64_t* buf1 = (uint64_t*)buf_;
	uint64_t* buf2 = (uint64_t*)(buf_ + size);
	uint32_t* out1 = out_crc32;
	uint32_t* out2 = out_crc32 + size / CRC32_BLOCK_SIZE;
	
	__builtin_prefetch(buf1, 0, 0);
	__builtin_prefetch(buf2, 0, 0);
	while((char*)buf1 < buf_ + size)
	{
		uint64_t crc1 = 0, crc2 = 0;
		for (uint32_t j=0; j < CRC32_BLOCK_LENGTH/8; ++j)
		{
			CRC32_P2(crc, buf, 0);
			CRC32_P2(crc, buf, 1);
			CRC32_P2(crc, buf, 2);
			CRC32_P2(crc, buf, 3);
			CRC32_P2(crc, buf, 4);
			CRC32_P2(crc, buf, 5);
			CRC32_P2(crc, buf, 6);
			CRC32_P2(crc, buf, 7);
			buf1 += 8;
			buf2 += 8;
			__builtin_prefetch(buf1, 0, 0);
			__builtin_prefetch(buf2, 0, 0);
		}
		*out1++ = (uint32_t)crc1;
		*out2++ = (uint32_t)crc2;
	}

	uint32_t remain = size0 - size * 2;
	if (remain > 0)
	{
		crc32_4k_sequence((char*)out2, remain, out2);
	}
}

#define CRC32_P3(crc, buf, index) { \
	CRC32(crc##1, buf##1, index); 	\
	CRC32(crc##2, buf##2, index); 	\
	CRC32(crc##3, buf##3, index); 	\
}

void crc32_4k_sequence_p3(char* buf_, uint32_t size, uint32_t* out_crc32)
{
	uint32_t size0 = size;
	size = size / CRC32_BLOCK_SIZE / 3 * CRC32_BLOCK_SIZE;
	uint64_t* buf1 = (uint64_t*)buf_;
	uint64_t* buf2 = (uint64_t*)(buf_ + size);
	uint64_t* buf3 = (uint64_t*)(buf_ + size * 2);
	uint32_t* out1 = out_crc32;
	uint32_t* out2 = out_crc32 + size / CRC32_BLOCK_SIZE;
	uint32_t* out3 = out_crc32 + size / CRC32_BLOCK_SIZE * 2;
	
	__builtin_prefetch(buf1, 0, 0);
	__builtin_prefetch(buf2, 0, 0);
	__builtin_prefetch(buf3, 0, 0);
	while((char*)buf1 < buf_ + size)
	{
		uint64_t crc1 = 0, crc2 = 0, crc3 = 0;
		for (uint32_t j=0; j < CRC32_BLOCK_LENGTH/8; ++j)
		{
			CRC32_P3(crc, buf, 0);
			CRC32_P3(crc, buf, 1);
			CRC32_P3(crc, buf, 2);
			CRC32_P3(crc, buf, 3);
			CRC32_P3(crc, buf, 4);
			CRC32_P3(crc, buf, 5);
			CRC32_P3(crc, buf, 6);
			CRC32_P3(crc, buf, 7);
			buf1 += 8;
			buf2 += 8;
			buf3 += 8;
			__builtin_prefetch(buf1, 0, 0);
			__builtin_prefetch(buf2, 0, 0);
			__builtin_prefetch(buf3, 0, 0);
		}
		*out1++ = (uint32_t)crc1;
		*out2++ = (uint32_t)crc2;
		*out3++ = (uint32_t)crc3;
	}

	uint32_t remain = size0 - size * 3;
	if (remain > 0)
	{
		crc32_4k_sequence((char*)buf3, remain, out3);
	}
}

#define CRC32_P4(crc, buf, index) { \
	CRC32(crc##1, buf##1, index); 	\
	CRC32(crc##2, buf##2, index); 	\
	CRC32(crc##3, buf##3, index); 	\
	CRC32(crc##4, buf##4, index); 	\
}

void crc32_4k_sequence_p4(char* buf_, uint32_t size, uint32_t* out_crc32)
{
	uint32_t size0 = size;
	size /= 4;
	uint64_t* buf1 = (uint64_t*)buf_;
	uint64_t* buf2 = (uint64_t*)(buf_ + size);
	uint64_t* buf3 = (uint64_t*)(buf_ + size * 2);
	uint64_t* buf4 = (uint64_t*)(buf_ + size * 3);
	uint32_t* out1 = out_crc32;
	uint32_t* out2 = out_crc32 + size / CRC32_BLOCK_SIZE;
	uint32_t* out3 = out_crc32 + size / CRC32_BLOCK_SIZE * 2;
	uint32_t* out4 = out_crc32 + size / CRC32_BLOCK_SIZE * 3;
	
	__builtin_prefetch(buf1, 0, 0);
	__builtin_prefetch(buf2, 0, 0);
	__builtin_prefetch(buf3, 0, 0);
	__builtin_prefetch(buf4, 0, 0);
	while((char*)buf1 < buf_ + size)
	{
		uint64_t crc1 = 0, crc2 = 0, crc3 = 0, crc4 = 0;
		for (uint32_t j=0; j < CRC32_BLOCK_LENGTH/8; ++j)
		{
			CRC32_P4(crc, buf, 0);
			CRC32_P4(crc, buf, 1);
			CRC32_P4(crc, buf, 2);
			CRC32_P4(crc, buf, 3);
			CRC32_P4(crc, buf, 4);
			CRC32_P4(crc, buf, 5);
			CRC32_P4(crc, buf, 6);
			CRC32_P4(crc, buf, 7);
			buf1 += 8;
			buf2 += 8;
			buf3 += 8;
			buf4 += 8;
			__builtin_prefetch(buf1, 0, 0);
			__builtin_prefetch(buf2, 0, 0);
			__builtin_prefetch(buf3, 0, 0);
			__builtin_prefetch(buf4, 0, 0);
		}
		*out1++ = (uint32_t)crc1;
		*out2++ = (uint32_t)crc2;
		*out3++ = (uint32_t)crc3;
		*out4++ = (uint32_t)crc4;
	}

	uint32_t remain = size0 - size * 4;
	if (remain > 0)
	{
		crc32_4k_sequence((char*)buf4, remain, out4);
	}
}
